package blockchain

import (
	"massnet.org/mass/logging"
	"massnet.org/mass/massutil"
	"massnet.org/mass/wire"
	"math/big"
)

// checkBlockSanity performs some preliminary checks on a block to ensure it is
// sane before continuing with block processing.  These checks are context free.
func checkBlockZMSanity(block *massutil.Block, chainID wire.Hash, pocLimit *big.Int, flags BehaviorFlags) error {
	msgBlock := block.MsgBlock()
	header := &msgBlock.Header
	proposals := &msgBlock.Proposals

	if !flags.isFlagSet(BFNoPoCCheck) {
		if err := checkBlockHeaderSanity(header, chainID, pocLimit, flags); err != nil {
			return err
		}
	}

	if err := checkBlockProposalSanity(proposals, header, chainID); err != nil {
		return err
	}

	// A block must have at least one transaction.
	numTx := len(msgBlock.Transactions)
	if numTx == 0 {
		return errBlockNoTransactions
	}

	// Checks that coinbase height matches block header height.
	if err := CheckCoinbaseHeight(block); err != nil {
		return err
	}

	// A block must not have more transactions than the max block payload.
	if numTx > wire.MaxTxPerBlock {
		logging.CPrint(logging.ERROR, "block contains too many transactions",
			logging.LogFormat{"numTx": numTx, "MaxTxPerBlock": wire.MaxTxPerBlock})
		return ErrTooManyTransactions
	}

	// A block must not exceed the maximum allowed block payload when
	// serialized.
	//serializedSize := msgBlock.PlainSize()
	serializedSize := msgBlock.PlainSize()
	//if serializedSize > wire.MaxBlockPayload
	if serializedSize > wire.MaxBlockPayload {
		logging.CPrint(logging.ERROR, "serialized block is too big",
			logging.LogFormat{"serializedSize": serializedSize, "MaxBlockPayload": wire.MaxBlockPayload})
		return ErrBlockTooBig
	}

	// ProposalRoot check
	proposalMerkles := wire.BuildMerkleTreeStoreForProposal(&block.MsgBlock().Proposals)
	calculatedProposalRoot := proposalMerkles[len(proposalMerkles)-1]
	if !header.ProposalRoot.IsEqual(calculatedProposalRoot) {
		logging.CPrint(logging.ERROR, "block proposal root is invalid",
			logging.LogFormat{"header.ProposalRoot": header.ProposalRoot, "calculate": calculatedProposalRoot})
		return ErrInvalidProposalRoot
	}

	// The first transaction in a block must be a coinbase.
	transactions := block.Transactions()
	if !IsCoinBase(transactions[0]) {
		return ErrFirstTxNotCoinbase
	}

	// A block must not have more than one coinbase.
	for i, tx := range transactions[1:] {
		if IsCoinBase(tx) {
			logging.CPrint(logging.ERROR, "block contains other coinbase",
				logging.LogFormat{"hindex": i})
			return ErrMultipleCoinbases
		}
	}

	// Do some preliminary checks on each transaction to ensure they are
	// sane before continuing.
	for _, tx := range transactions {
		err := CheckTransactionSanity(tx)
		if err != nil {
			return err
		}
	}

	// Build merkle tree and ensure the calculated merkle root matches the
	// entry in the block header.  This also has the effect of caching all
	// of the transaction hashes in the block to speed up future hash
	// checks.  Massd builds the tree here and checks the merkle root
	// after the following checks, but there is no reason not to check the
	// merkle root matches here.
	merkles := wire.BuildMerkleTreeStoreTransactions(block.MsgBlock().Transactions, false)
	calculatedMerkleRoot := merkles[len(merkles)-1]
	if !header.TransactionRoot.IsEqual(calculatedMerkleRoot) {
		logging.CPrint(logging.ERROR, "block merkle root is invalid",
			logging.LogFormat{"header.TransactionRoot": header.TransactionRoot, "calculate": calculatedMerkleRoot})
		return ErrInvalidMerkleRoot
	}

	witnessMerkles := wire.BuildMerkleTreeStoreTransactions(block.MsgBlock().Transactions, true)
	witnessMerkleRoot := witnessMerkles[len(witnessMerkles)-1]
	if !header.WitnessRoot.IsEqual(witnessMerkleRoot) {
		logging.CPrint(logging.ERROR, "block witness merkle root is invalid",
			logging.LogFormat{"header.WitnessRoot": header.WitnessRoot, "calculate": witnessMerkleRoot})
		return ErrInvalidMerkleRoot
	}

	// Check for duplicate transactions.  This check will be fairly quick
	// since the transaction hashes are already cached due to building the
	// merkle tree above.
	existingTxHashes := make(map[wire.Hash]struct{})
	for i, tx := range transactions {
		hash := tx.Hash()
		if _, exists := existingTxHashes[*hash]; exists {
			logging.CPrint(logging.ERROR, "block contains duplicate transaction",
				logging.LogFormat{"transaction": hash, "index": i})
			return ErrDuplicateTx
		}
		existingTxHashes[*hash] = struct{}{}
	}

	// The number of signature operations must be less than the maximum
	// allowed per block.
	totalSigOps := 0
	for _, tx := range transactions {
		// We could potentially overflow the accumulator so check for
		// overflow.
		lastSigOps := totalSigOps

		//witness-totalSigOps += CountSigOps(tx)
		totalSigOps += CountSigOps(tx)
		if totalSigOps < lastSigOps || totalSigOps > MaxSigOpsPerBlock {
			logging.CPrint(logging.ERROR, "block contains too many signature operations",
				logging.LogFormat{"totalSigOps": totalSigOps, "maxSigOps": MaxSigOpsPerBlock})
			return ErrTooManySigOps
		}
	}

	return nil
}
